iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0
Vibe Coding

從 GAS 到 Cloudflare D1:AI Agent 如何打造、接手並重構真實便當系統系列 第 7

Day 7|一套便當系統,為什麼最後會碰到身份系統?

  • 分享至 

  • xImage
  •  

Day 7

Day 6 談到,一整段 User Journey 裡反覆累積的等待,已經逼著系統重新評估 GAS + Sheets 的執行模型。

架構開始移動之後,另一個比「速度」更根本的問題也浮了出來:

同一個人,可以從不同入口進來。

有人從員工編號登入。

有人從 LINE 進來。

也有人今天用員編,明天又從 LINE 打開。

一開始,這件事很容易被理解成:

系統多支援一種登入方式。

但真的踩過 Bug 之後才會發現,問題不在「多一種登入」,而在「多個入口最後是否收斂到同一個人」。

因為對便當系統來說,登入方式只是入口。

訂單、餘額、角色與歷史需要知道的是:

不管你從哪裡進來,系統最後認得的是不是同一個人?

這也是這套便當系統第一次碰到「Identity」問題。


最容易低估的地方:登入成功,不代表身份已經解決

如果只看登入畫面,事情很單純。

員工編號
→ 登入成功

LINE
→ 登入成功

兩條路都能走進系統,看起來就完成了。

但只要系統開始保存狀態,問題就會立刻變成:

員編 123456
LINE User A

到底是:

兩個使用者?

還是:

同一個人的兩種入口?

如果這件事沒有回答清楚,後面的每個功能都可能出問題。

例如:

  • 今天用員編下的訂單,明天從 LINE 進來看不看得到?
  • 餘額應該掛在哪一個身份上?
  • LINE 綁定後,要不要再建立一個新使用者?
  • 權限是認員編、LINE,還是系統裡真正的 User?
  • 未來如果多一種登入方式,又要不要再複製一份資料?

因此,這三件事需要被拆開看:

層次 它回答的問題
Authentication 你這次是怎麼證明自己可以進來?
Identity Resolution 這個入口對應到系統裡哪一個人?
Onboarding 如果還缺資料,要補完哪些步驟才能開始使用?

這三件事很容易被塞進同一段登入流程。

但它們其實不是同一件事。


核心是 canonical user

這套模型最容易理解的方式是:

員編與 LINE 都不是使用者本身,它們只是用來找到使用者的入口。

也就是:

員編登入 ─┐
          ├─→ canonical user
LINE 登入 ─┘

canonical user 才是系統內應該承擔責任的身份。

訂單屬於它。

餘額屬於它。

一般使用者角色屬於它。

歷史也應該回到它。

這樣一來,登入方式就變成:

如何找到同一個 canonical user。

而不是:

每增加一種登入方式,就增加一種 User。

這個差別看起來只是資料模型的文字遊戲,但實際上會直接決定系統會不會產生重複身份。

Day 7 Identity|多入口收斂到 canonical user

不同登入入口最後都應收斂到同一個 canonical user;訂單、餘額、權限與歷史跟著「人」走,而不是跟著某一種登入方式走。


撞到的 Bug,反而不是 canonical user 壞掉

這套身份邏輯後來曾經出現一個很典型的問題。

新的 Normal User 第一次用員工編號進來後,系統其實已經建立了對應的 canonical user,也有 employee guest session。

照需求來說,這時候一般使用者應該可以直接進入主畫面。

LINE 綁定可以之後再做。

但實際流程卻曾經繼續把使用者導向 LINE 綁定。

表面看起來很像:

員編登入沒有真的完成。

只看現象,很容易直接往 Backend 查:

  • Worker 有沒有建立 User?
  • session 有沒有寫成功?
  • bootstrap 有沒有漏資料?
  • canonical user 是否沒有 resolve 到?

但 bounded trace 後看到的結果不是這樣。

問題出在前端狀態映射。

某個狀態組合是:

employee_guest
+
user = null

它被前端解讀成:

尚未註冊
→ 繼續走 LINE 綁定

但這個判斷把兩個不同問題混在一起了。

employee_guest 描述的是:

這次從哪一種 authentication entry 進來。

而 user = null 在那個流程節點,也不能單獨代表:

系統裡沒有 canonical user。

結果就是:

Backend 的身份關係其實可以成立,Frontend 卻把中間狀態翻譯成錯的使用者旅程。

這次修正後,一般 Normal User 可以用員編或 LINE 其中一種方式進入;LINE 綁定不再是員編首次登入的強制前置條件。


這次 Bug 最有價值的地方,不是修了一個 redirect

如果只是描述結果,它很容易被寫成:

修正員編登入後錯誤跳轉 LINE

更值得留下來的工程結論是另一件事:

Identity Bug 不一定發生在「驗證身份」的那一層。

它可能發生在:

  • authentication
  • session restore
  • canonical user resolution
  • bootstrap
  • frontend state mapping
  • onboarding routing

任何一層。

而且它們在畫面上的症狀可能非常像。

使用者看到的都只是:

我怎麼又被叫去登入?

因此,遇到身份問題時,第一個問題不該是:

哪一段登入程式要改?

而是先把 trace 拆開。


先追這條 Identity Trace

遇到這類問題時,可以先畫出一條很簡單的鏈:

Entry
↓
Authentication
↓
Session
↓
Canonical User Resolution
↓
Bootstrap / Identity State
↓
Frontend Routing
↓
Main UI / Onboarding

然後逐層問:

1. Entry:使用者從哪裡進來?

例如:

employee ID
LINE

這只是入口。

先不要把它直接等同於 User。

2. Authentication:這次憑什麼相信他?

例如:

  • 員編輸入與 lookup
  • LINE authentication

這一層只回答:

這個 entry 可不可以成立?

3. Session:這次已經建立什麼登入狀態?

如果 session 根本沒留下來,後面當然全部會失敗。

但如果 session 正常,就不要一直重查 Authentication。

4. Canonical User Resolution:最後指向誰?

這是整條線最重要的一層。

要確認:

employee entry
→ user X

LINE entry
→ user X

而不是:

employee entry
→ user X

LINE entry
→ user Y

5. Bootstrap / Identity State:後端回來的是什麼狀態?

這一層很容易開始出現:

  • 已存在使用者
  • 新使用者
  • employee guest
  • 尚缺 binding
  • unregistered

之類的語意。

問題是這些狀態必須有清楚 contract。

6. Frontend Routing:前端怎麼解讀它?

這次 Bug 留下的提醒是:

Backend 回對,不代表畫面一定走對。

只要前端自己多推論一步,就可能重新發明一套身份規則。


一個很重要的原則:不要讓每一層都自己猜「你是誰」

身份系統最危險的狀態,不一定是少一個判斷。

有時候反而是判斷太多。

例如:

Worker 判斷 canonical user
Frontend 再看 authMode 猜一次
Bootstrap component 再看 user 是否為 null 猜一次
Routing 再猜一次該不該去 onboarding

每一段單看都很合理。

但只要它們對同一個狀態的理解不完全一樣,就會出現:

多個地方都在回答同一個問題,最後卻給出不同答案。

因此,更穩定的做法是把責任拆清楚:

問題 應該由誰主要回答
這次如何登入? Authentication
這個入口對應哪個 User? Identity Resolution
使用者目前缺什麼? Identity / Onboarding State
畫面接下來去哪? Frontend 依 contract 呈現

Frontend 可以決定 UX。

但不應該在沒有 authority 的情況下重新推導 canonical identity。


Authentication、Identity、Onboarding 一定要分開

這是 Day 7 最重要的判斷框架。

假設使用者第一次從員編登入:

Authentication
= 成功

這不等於:

Identity
= 一定是新 User

也不等於:

Onboarding
= 必須綁 LINE

三者要分開。

例如一般使用者目前的策略可以理解成:

員編
  ↓
找到 / 建立 canonical user
  ↓
完成必要 onboarding
  ↓
可進入一般功能

LINE binding
= optional enhancement

而不是:

員編
↓
一定要 LINE
↓
才算完整 User

這個差異很重要。

因為如果把某一種 authentication method 寫成整個 Identity lifecycle 的必經之路,未來每次調整登入政策都會牽動整套使用者模型。


但「多入口」也不是越自由越好

做到這裡,很容易走到另一個極端:

既然員編和 LINE 都只是入口,那全部都放寬就好了。

也不對。

Identity Resolution 越自由,越需要處理:

  • ownership conflict
  • duplicate user
  • account takeover
  • role escalation
  • 綁錯 LINE 後的修復
  • 高權限身份如何驗證

這套便當系統目前也刻意區分一般 User 與更高權限角色。

一般使用者可以讓員編與 LINE 比較彈性地指向同一 canonical user。

但 Admin / ProxyAdmin 的身份與授權仍需要更嚴格的保護。

所以 Day 7 的結論不是:

登入越方便越好。

而是:

入口可以有很多個,但每一個入口如何收斂到 canonical user,必須有單一而清楚的規則。

方便是 UX。

身份一致性與權限安全是另一個責任。

兩者不能混成一句「登入成功就好了」。


如果系統只有一種登入,其實不用急著做 Identity Platform

這裡也要保留一個邊界。

不是所有小系統一開始都需要:

  • Identity Provider
  • Account Linking
  • 複雜身份圖
  • 多階段狀態機

如果一個系統永遠只有單一入口,而且沒有跨入口共用資料的需求,直接使用簡單登入可能完全足夠。

需要正式建模 Identity 的訊號,通常是:

  1. 同一個人開始有兩種以上入口。
  2. 訂單、餘額、權限或歷史必須跨入口保持一致。
  3. 不同登入方式的生命週期不同。
  4. 入口與角色之間不能直接畫等號。
  5. Frontend / Backend 已經開始各自猜使用者狀態。

當這些事情開始出現,再把 canonical user 與狀態 contract 拉出來,通常比一開始就建立完整身份平台更合理。


系統碰到 Identity,不是因為我想做登入功能

Day 5 的多店家留下了一個結論:

原本藏在環境裡的常數,最後會變成 Domain Data。

Day 6 的等待則證明:

功能可以完成,不代表整段 User Journey 已經足夠好。

到了 Day 7,身份系統再把問題往前推了一層。

一開始「你是誰」好像只是登入畫面的問題。

但只要訂單、餘額、權限與歷史都要跟著同一個人走,

它就會從:

登入方式

一路長成:

Authentication
↓
Identity Resolution
↓
Canonical User
↓
Onboarding State
↓
Authorization / Domain Data

這套系統不是因為想做 Identity Platform 才變複雜。

而是當真實使用者開始從不同入口進來之後,

原本那句:

「登入成功就好。」

已經不夠回答系統需要知道的事。

要回答的是:

不管你從哪裡進來,我能不能確定你仍然是同一個人?

這件事一旦答錯,壞掉的不只是一個登入畫面。

後面的訂單、餘額、權限與歷史,都可能跟著認錯人。


本系列實作專案

這不是純概念示範,而是一套持續開發中的便當訂購系統。文章著重「為什麼」,GitHub 保留實際程式碼、文件與演進紀錄。

GitHub:https://github.com/henryfir456/bento-order-app


下一篇

身份回答的是:

這筆資料到底屬於誰?

但便當系統裡還有另一種資料,一旦開始出現,就不能只把它當成普通欄位。

那就是:

錢。

當「目前餘額」和一筆一筆的儲值、扣款、調整同時存在時,我也開始碰到另一個問題:

到底哪一份資料才算帳?


上一篇
Day 6|功能都正常,為什麼我還是決定離開 GAS?
下一篇
Day 8|餘額一進來,系統就不再只是表單
系列文
從 GAS 到 Cloudflare D1:AI Agent 如何打造、接手並重構真實便當系統9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言